package com.zbkj.client.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.zbkj.common.constants.Constants;
import com.zbkj.common.model.admin.SystemAdmin;
import com.zbkj.common.model.merchant.Merchant;
import com.zbkj.common.response.ClientLoginResponse;
import com.zbkj.common.utils.RedisUtil;
import com.zbkj.common.vo.LoginUserVo;
import com.zbkj.service.service.MerchantService;
import com.zbkj.service.service.SystemAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * 客户端token验证处理
 * +----------------------------------------------------------------------
 * | CRMEB [ CRMEB赋能开发者，助力企业发展 ]
 * +----------------------------------------------------------------------
 * | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
 * +----------------------------------------------------------------------
 * | Licensed CRMEB并不是自由软件，未经许可不能去掉CRMEB相关版权
 * +----------------------------------------------------------------------
 * | Author: CRMEB Team <admin@crmeb.com>
 * +----------------------------------------------------------------------
 */
@Component
public class ClientTokenComponent {

    @Resource
    private RedisUtil redisUtil;

    @Autowired
    private SystemAdminService systemAdminService;

    @Autowired
    private MerchantService merchantService;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    private static final Long MILLIS_MINUTE = 60 * 1000L;

    // 令牌有效期（默认30分钟）
    private static final int expireTime = 30;


    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public Integer getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StrUtil.isNotBlank(token)) {
            return redisUtil.get(token);
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUserVo loginUser) {
        if (ObjectUtil.isNotNull(loginUser) && StrUtil.isNotBlank(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(LoginUserVo loginUserVo) {
        if (ObjectUtil.isNotNull(loginUserVo)) {
            redisUtil.delete(getClientTokenKey(loginUserVo.getToken()));
        }
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser LoginUserVo
     */
    public void verifyToken(LoginUserVo loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUserVo loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uid将loginUser缓存
        String userKey = getClientTokenKey(loginUser.getToken());
        redisUtil.set(userKey, loginUser.getUser().getId(), (long) expireTime, TimeUnit.MINUTES);
    }

    /**
     * 获取请求token
     *
     * @param request HttpServletRequest
     * @return token
     */
    public String getToken(HttpServletRequest request) {
        String token = request.getHeader(Constants.CLIENT_HEADER_AUTHORIZATION_KEY);
        if (StrUtil.isEmpty(token)) {
            return token;
        }

        return getClientTokenKey(token);
    }

    private String getClientTokenKey(String token) {
        return Constants.CLIENT_TOKEN_KEY + token;
    }

    /**
     * 获取用户信息，如果redis中没该用户信息，则从数据库中取出用户信息写入到redis中
     * @param userId
     * @return
     */
    public ClientLoginResponse getUserInfo(Integer userId, String token) {
        String userKey = Constants.CLIENT_USER_INFO_KEY + userId;

        ClientLoginResponse clientLoginRedis = redisUtil.get(userKey);
        if(null == clientLoginRedis){
            SystemAdmin systemAdmin = systemAdminService.getById(userId);
            ClientLoginResponse clientLogin = new ClientLoginResponse();

            clientLogin.setRealName(systemAdmin.getRealName());
            clientLogin.setId(userId);
            clientLogin.setUsername(systemAdmin.getAccount());
            clientLogin.setPhone(systemAdmin.getPhone());
            clientLogin.setMerId(systemAdmin.getMerId());
            clientLogin.setToken(token);
            clientLogin.setHeadImg(systemAdmin.getHeaderImage());
            clientLogin.setPackageLevel(systemAdmin.getPackageLevel()); //套餐等级，1-4
            clientLogin.setType(systemAdmin.getType()); //2-管理员账号，4-子账号
            clientLogin.setExpiredTime(systemAdmin.getExpiredTime()); //套餐过期时间
            redisUtil.set(userKey, clientLogin, (long) expireTime, TimeUnit.MINUTES);

            return clientLogin;
        }

        return clientLoginRedis;
    }

    /**
     * 通过token获取用户ID
     * 用于WebSocket连接验证
     * @param token 用户token
     * @return 用户ID，如果token无效则返回null
     */
    public Integer getUserIdByToken(String token) {
        if (StrUtil.isBlank(token)) {
            return null;
        }
        
        try {
            // 构造Redis key
            String userKey = getClientTokenKey(token);
            // 从Redis中获取用户ID
            Integer userId = redisUtil.get(userKey);
            
            if (userId != null) {
                // 验证用户是否存在
                SystemAdmin systemAdmin = systemAdminService.getById(userId);
                if (systemAdmin != null) {
                    return userId;
                }
            }
            
            return null;
        } catch (Exception e) {
            // 记录日志但不抛出异常
            System.err.println("Token验证失败: " + e.getMessage());
            return null;
        }
    }

    /**
     * 根据merId获取该租户的点数
     * 1. 如果redis中有该值，则直接返回
     * 2. 如果redis中没有则从表eb_merchant中获取，同时将表中的值写入到redis中
     * 3. redis的key设置为 C:M:{merId}:算力值点
     * 4. 表eb_merchant中power_point字段为该租户下所有用户的点数
     * 
     * @param merId 商户ID
     * @return 用户的算力点数（浮点类型）
     */
    public Double getMerchantPowerPoint(Integer merId) {
        if (merId == null) {
            return 0.0;
        }

        // 构造Redis key
        String redisKey = "C:M:" + merId;
        
        // 先从Redis中获取
        Double powerPoint = redisUtil.get(redisKey);
        if (powerPoint != null) {
            return powerPoint;
        }

        // Redis中没有，从数据库中获取
        try {
            Merchant merchant = merchantService.getById(merId);
            if (merchant != null && merchant.getPowerPoint() != null) {
                // 将数据库中的值转换为Double
                powerPoint = merchant.getPowerPoint().doubleValue();
                
                // 将值写入Redis，设置过期时间为30分钟
                redisUtil.set(redisKey, powerPoint, (long) expireTime, TimeUnit.MINUTES);
                
                return powerPoint;
            }
        } catch (Exception e) {
            // 记录异常但不影响返回
            System.err.println("获取商户点数失败，merId: " + merId + ", 错误: " + e.getMessage());
        }

        // 如果获取失败，返回0.0
        return 0.0;
    }

    /**
     * 根据merId更新该租户的点数
     * 1. 如果redis中key=C:M:{merId}有值，则先更新redis中的值，再更新数据库中的值
     * 2. 如果redis中key=C:M:{merId}没有值，则先更新数据库，将数据库中更新后的数据写入到redis中
     * 3. redis中更新的规则为，(原来的值-costPoint) 作为新的值
     * 4. 数据库更新表规则为：eb_merchant中power_point字段为(原来的值-costPoint)
     * 
     * @param merId 租户ID
     * @param costPoint 扣的点数
     * @return 更新后的用户算力点数
     */
    public Double updateMerchantPowerPoint(Integer merId, Double costPoint) {
        if (merId == null) {
            throw new IllegalArgumentException("商户ID不能为空");
        }
        if (costPoint == null || costPoint < 0) {
            throw new IllegalArgumentException("扣点数不能为空且不能为负数");
        }

        // 构造Redis key
        String redisKey = "C:M:" + merId;
        
        // 先从Redis中获取当前值
        Double currentPowerPoint = redisUtil.get(redisKey);
        
        if (currentPowerPoint != null) {
            // Redis中有值，先更新Redis，再更新数据库
            Double newPowerPoint = currentPowerPoint - costPoint;
            
            // 确保点数不会为负数
            if (newPowerPoint < 0) {
                newPowerPoint = 0.0;
            }
            
            // 更新Redis
            redisUtil.set(redisKey, newPowerPoint, (long) expireTime, TimeUnit.MINUTES);
            
            // 更新数据库
            try {
                Merchant merchant = merchantService.getById(merId);
                if (merchant != null) {
                    merchant.setPowerPoint(new java.math.BigDecimal(newPowerPoint));
                    merchantService.updateById(merchant);
                }
            } catch (Exception e) {
                // 如果数据库更新失败，回滚Redis
                redisUtil.set(redisKey, currentPowerPoint, (long) expireTime, TimeUnit.MINUTES);
                throw new RuntimeException("更新商户点数失败，merId: " + merId + ", 错误: " + e.getMessage(), e);
            }
            
            return newPowerPoint;
        } else {
            // Redis中没有值，先更新数据库，再写入Redis
            try {
                Merchant merchant = merchantService.getById(merId);
                if (merchant == null) {
                    throw new RuntimeException("商户不存在，merId: " + merId);
                }
                
                // 获取数据库中的当前值
                Double dbCurrentPowerPoint = 0.0;
                if (merchant.getPowerPoint() != null) {
                    dbCurrentPowerPoint = merchant.getPowerPoint().doubleValue();
                }
                
                // 计算新值
                Double newPowerPoint = dbCurrentPowerPoint - costPoint;
                
                // 确保点数不会为负数
                if (newPowerPoint < 0) {
                    newPowerPoint = 0.0;
                }
                
                // 更新数据库
                merchant.setPowerPoint(new java.math.BigDecimal(newPowerPoint));
                merchantService.updateById(merchant);
                
                // 将更新后的值写入Redis
                redisUtil.set(redisKey, newPowerPoint, (long) expireTime, TimeUnit.MINUTES);
                
                return newPowerPoint;
                
            } catch (Exception e) {
                throw new RuntimeException("更新商户点数失败，merId: " + merId + ", 错误: " + e.getMessage(), e);
            }
        }
    }

	/**
	 * 更新Redis中的用户头像
	 * @param userId 用户ID
	 * @param headImg 新头像地址
	 */
	public void updateUserHeadImg(Integer userId, String headImg) {
		if (userId == null || StrUtil.isBlank(headImg)) {
			return;
		}
		String userKey = Constants.CLIENT_USER_INFO_KEY + userId;
		ClientLoginResponse clientLoginRedis = redisUtil.get(userKey);
		if (clientLoginRedis != null) {
			clientLoginRedis.setHeadImg(headImg);
			redisUtil.set(userKey, clientLoginRedis, (long) expireTime, TimeUnit.MINUTES);
			return;
		}
		// 如果缓存不存在，从数据库载入并写回缓存
		SystemAdmin systemAdmin = systemAdminService.getById(userId);
		if (systemAdmin != null) {
			ClientLoginResponse clientLogin = new ClientLoginResponse();
			clientLogin.setRealName(systemAdmin.getRealName());
			clientLogin.setId(userId);
			clientLogin.setUsername(systemAdmin.getAccount());
			clientLogin.setPhone(systemAdmin.getPhone());
			clientLogin.setMerId(systemAdmin.getMerId());
			clientLogin.setHeadImg(headImg);
			clientLogin.setPackageLevel(systemAdmin.getPackageLevel());
			clientLogin.setType(systemAdmin.getType());
			redisUtil.set(userKey, clientLogin, (long) expireTime, TimeUnit.MINUTES);
		}
	}
} 